/* gtktransitionstate.c
 *
 * Copyright (C) 2007 Andre Moreira Magalhaes <andrunko@gmail.com>
 * Copyright (C) 2007 Renato Araujo Oliveira Filho <renatox@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gtktransitionstate.h"

#include <gobject/gvaluecollector.h>

#include <string.h>

typedef struct _GtkTransitionStatePrivate     GtkTransitionStatePrivate;
typedef struct _GtkTransitionStateProperty    GtkTransitionStateProperty;

struct _GtkTransitionStatePrivate
{
  GObject *object;
  GSList *properties;
};

struct _GtkTransitionStateProperty
{
  gchar *name;
  GValue v_start;
  GValue v_end;
  GtkTransitionStateProgressFunc progress_func;
  gpointer user_data;
};

enum {
  PROP_0,
  PROP_OBJECT,
};

#define GTK_TRANSITION_STATE_GET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_TRANSITION_STATE, GtkTransitionStatePrivate))

/* GTK_TRANSITION_STATE_COLLECT_PROPERTY() collects a property
 * we have to implement the var_args collection as a
 * macro, because on some systems va_list variables cannot be passed
 * by reference.
 */
#define GTK_TRANSITION_STATE_COLLECT_PROPERTY(state, object, name, var_args, g_type, properties, property) \
G_STMT_START {                                                                                             \
  do                                                                                                       \
    {                                                                                                      \
      GSList *_properties;                                                                                 \
      GtkTransitionStateProperty *_property;                                                               \
      gchar *_error;                                                                                       \
      if (*g_type == G_TYPE_NONE)                                                                          \
        {                                                                                                  \
          GParamSpec *pspec;                                                                               \
          pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object),                               \
                                                name);                                                     \
          if (!pspec)                                                                                      \
            {                                                                                              \
              g_warning ("%s: object has no property named %s", G_STRFUNC, name);                          \
              break;                                                                                       \
            }                                                                                              \
          *g_type = G_PARAM_SPEC_VALUE_TYPE (pspec);                                                       \
        }                                                                                                  \
      _properties = g_slist_find_custom (properties, name,                                                 \
                                         (GCompareFunc) gtk_transition_state_property_compare_func);       \
      if (!_properties)                                                                                    \
        {                                                                                                  \
          _property = g_new0 (GtkTransitionStateProperty, 1);                                              \
          _property->name = g_strdup (name);                                                               \
        }                                                                                                  \
      else                                                                                                 \
        _property = (GtkTransitionStateProperty *) _properties->data;                                      \
      _error = NULL;                                                                                       \
      if (G_IS_VALUE (&_property->v_start))                                                                \
        g_value_unset (&_property->v_start);                                                               \
      g_value_init (&_property->v_start, *g_type);                                                         \
      G_VALUE_COLLECT (&_property->v_start, var_args, 0, &_error);                                         \
      if (_error)                                                                                          \
        {                                                                                                  \
          g_warning ("%s: %s", G_STRFUNC, _error);                                                         \
          g_free (_error);                                                                                 \
          gtk_transition_state_property_destroy (_property);                                               \
          break;                                                                                           \
        }                                                                                                  \
      _error = NULL;                                                                                       \
      if (G_IS_VALUE (&_property->v_end))                                                                  \
        g_value_unset (&_property->v_end);                                                                 \
      g_value_init (&_property->v_end, *g_type);                                                           \
      G_VALUE_COLLECT (&_property->v_end, var_args, 0, &_error);                                           \
      if (_error)                                                                                          \
        {                                                                                                  \
          g_warning ("%s: %s", G_STRFUNC, _error);                                                         \
          g_free (_error);                                                                                 \
          gtk_transition_state_property_destroy (_property);                                               \
          break;                                                                                           \
        }                                                                                                  \
      *property = _property;                                                                               \
    }                                                                                                      \
  while (0);                                                                                               \
} G_STMT_END

static void gtk_transition_state_class_init                 (GtkTransitionStateClass    *klass);
static void gtk_transition_state_init                       (GtkTransitionState         *state);
static void gtk_transition_state_dispose                    (GObject                    *object);
static void gtk_transition_state_set_property               (GObject                    *object,
                                                             guint                       prop_id,
                                                             const GValue               *value,
                                                             GParamSpec                 *pspec);
static void gtk_transition_state_get_property               (GObject                    *object,
                                                             guint                       prop_id,
                                                             GValue                     *value,
                                                             GParamSpec                 *pspec);
static int  gtk_transition_state_property_compare_func      (GtkTransitionStateProperty *property,
                                                             const gchar                *property_name);
static GtkTransitionStateProgressFunc
            gtk_transition_state_get_progress_func_for_type (GtkTransitionState         *state,
                                                             GType                       type);
static void gtk_transition_state_int_progress_func          (GtkTransitionState         *state,
                                                             const gchar                *property_name,
                                                             gdouble                     progress,
                                                             GValue                     *v_result);
static void gtk_transition_state_double_progress_func       (GtkTransitionState         *state,
                                                             const gchar                *property_name,
                                                             gdouble                     progress,
                                                             GValue                     *v_result);
static void gtk_transition_state_boolean_progress_func      (GtkTransitionState         *state,
                                                             const gchar                *property_name,
                                                             gdouble                     progress,
                                                             GValue                     *v_result);
static void gtk_transition_state_property_destroy           (GtkTransitionStateProperty *property);


G_DEFINE_TYPE (GtkTransitionState, gtk_transition_state, G_TYPE_OBJECT);

static void
gtk_transition_state_class_init (GtkTransitionStateClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  g_type_class_add_private (klass, sizeof (GtkTransitionStatePrivate));

  object_class->dispose = gtk_transition_state_dispose;
  object_class->set_property = gtk_transition_state_set_property;
  object_class->get_property = gtk_transition_state_get_property;

  g_object_class_install_property (object_class,
                                   PROP_OBJECT,
                                   g_param_spec_object ("object",
                                                        "object",
                                                        "Object to be used by this state",
                                                        G_TYPE_OBJECT,
                                                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
}

static void
gtk_transition_state_init (GtkTransitionState *state)
{
}

static void
gtk_transition_state_dispose (GObject *object)
{
  GtkTransitionStatePrivate *priv = GTK_TRANSITION_STATE_GET_PRIVATE (object);
  GSList *properties;
  GtkTransitionStateProperty *property;

  g_object_unref (priv->object);
  for (properties = priv->properties; properties; properties = properties->next)
    {
      property = (GtkTransitionStateProperty *) properties->data;
      gtk_transition_state_property_destroy (property);
    }
  g_slist_free (priv->properties);

  G_OBJECT_CLASS (gtk_transition_state_parent_class)->dispose (object);
}

static void
gtk_transition_state_set_property (GObject      *object,
                                   guint         prop_id,
                                   const GValue *value,
                                   GParamSpec   *pspec)
{
  GtkTransitionState *state;
  GtkTransitionStatePrivate *priv;

  state = GTK_TRANSITION_STATE (object);
  priv = GTK_TRANSITION_STATE_GET_PRIVATE (state);

  switch (prop_id)
    {
    case PROP_OBJECT:
      priv->object = g_value_dup_object (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
gtk_transition_state_get_property (GObject    *object,
                                   guint       prop_id,
                                   GValue     *value,
                                   GParamSpec *pspec)
{
  GtkTransitionState *state;
  GtkTransitionStatePrivate *priv;

  state = GTK_TRANSITION_STATE (object);
  priv = GTK_TRANSITION_STATE_GET_PRIVATE (state);

  switch (prop_id)
    {
    case PROP_OBJECT:
      g_value_set_object (value, priv->object);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

/**
 * gtk_transition_state_new:
 * @object: #GObject in which the state will be applied to
 * @returns: a newly created #GtkTransitionState
 *
 * Creates a new #GtkTransitionState for the specified object.
 **/
GtkTransitionState *
gtk_transition_state_new (GObject *object)
{
  g_return_val_if_fail (G_IS_OBJECT (object), NULL);

  return g_object_new (GTK_TYPE_TRANSITION_STATE,
                       "object", object,
                       NULL);
}

/**
 * gtk_transition_state_get_object:
 * @state: a #GtkTransitionState
 * @returns: the #GObject in which the state applies to
 *
 * Gets the #GObject in which the @state will be applied to.
 *
 * The returned object does not have a reference added, so you
 * do not need to unref it.
 **/
GObject *
gtk_transition_state_get_object (GtkTransitionState *state)
{
  GtkTransitionStatePrivate *priv;

  g_return_val_if_fail (GTK_IS_TRANSITION_STATE (state), NULL);

  priv = GTK_TRANSITION_STATE_GET_PRIVATE (state);

  return priv->object;
}

void
gtk_transition_state_get_range (GtkTransitionState  *state,
                                const gchar         *property_name,
                                GValue             **v_start,
                                GValue             **v_end)
{
  GtkTransitionStatePrivate *priv;
  GSList *properties;
  GtkTransitionStateProperty *property;

  g_return_if_fail (GTK_IS_TRANSITION_STATE (state));
  g_return_if_fail (property_name != NULL);
  g_return_if_fail (v_start != NULL);
  g_return_if_fail (v_end != NULL);

  priv = GTK_TRANSITION_STATE_GET_PRIVATE (state);

  properties = g_slist_find_custom (priv->properties, property_name,
                                    (GCompareFunc) gtk_transition_state_property_compare_func);
  if (properties)
    {
      property = (GtkTransitionStateProperty *) properties->data;
      *v_start = &property->v_start;
      *v_end = &property->v_end;
    }
}

void
gtk_transition_state_set (GtkTransitionState *state,
                          const gchar        *first_property_name,
                          ...)
{
  GtkTransitionStatePrivate *priv;
  GtkTransitionStateProperty *property;
  va_list var_args;
  const gchar *name;
  GType g_type;

  g_return_if_fail (GTK_IS_TRANSITION_STATE (state));
  g_return_if_fail (first_property_name != NULL);

  priv = GTK_TRANSITION_STATE_GET_PRIVATE (state);

  va_start (var_args, first_property_name);
  name = first_property_name;
  while (name)
    {
      g_type = G_TYPE_NONE;

      property = NULL;
      GTK_TRANSITION_STATE_COLLECT_PROPERTY (state,
                                             priv->object,
                                             name,
                                             var_args, &g_type,
                                             priv->properties,
                                             &property);
      if (property == NULL)
        break;

      property->progress_func =
          gtk_transition_state_get_progress_func_for_type (state,
                                                           g_type);
      if (!property->progress_func)
        {
          /* just ignore this property */
          g_warning ("%s: unable to get progress func for GType %s",
                     G_STRFUNC, g_type_name (g_type));
          gtk_transition_state_property_destroy (property);
        }
      else
        {
          priv->properties = g_slist_append (priv->properties,
                                             property);
        }

      name = va_arg (var_args, gchar*);
    }
  va_end (var_args);
}

void
gtk_transition_state_set_full (GtkTransitionState *state,
                               const gchar        *first_property_name,
                               ...)
{
  GtkTransitionStatePrivate *priv;
  GtkTransitionStateProperty *property;
  va_list var_args;
  const gchar *name;
  GType g_type;

  g_return_if_fail (GTK_IS_TRANSITION_STATE (state));
  g_return_if_fail (first_property_name != NULL);

  priv = GTK_TRANSITION_STATE_GET_PRIVATE (state);

  va_start (var_args, first_property_name);
  name = first_property_name;
  while (name)
    {
      g_type = va_arg(var_args, GType);

      property = NULL;
      GTK_TRANSITION_STATE_COLLECT_PROPERTY (state,
                                             priv->object,
                                             name,
                                             var_args, &g_type,
                                             priv->properties,
                                             &property);
      if (property == NULL)
        break;

      property->progress_func =
          va_arg (var_args, GtkTransitionStateProgressFunc);
      if (!property->progress_func)
        {
          g_warning ("%s: progress func not supplied",
                     G_STRFUNC);
          gtk_transition_state_property_destroy (property);
          break;
        }

      priv->properties = g_slist_append (priv->properties,
                                         property);
      name = va_arg (var_args, gchar*);
    }
  va_end (var_args);
}

void
gtk_transition_state_set_progress (GtkTransitionState *state,
                                   gdouble             progress)
{
  GtkTransitionStatePrivate *priv;
  GSList *properties;
  GtkTransitionStateProperty *property;
  GValue value = { 0 };

  g_return_if_fail (GTK_IS_TRANSITION_STATE (state));
  g_return_if_fail (progress >= 0.0 && progress <= 1.0);

  priv = GTK_TRANSITION_STATE_GET_PRIVATE (state);

  for (properties = priv->properties; properties; properties = properties->next)
    {
      property = (GtkTransitionStateProperty *) properties->data;
      property->progress_func (state, property->name, progress, &value);
      g_object_set_property (priv->object, property->name, &value);
      g_value_unset (&value);
    }
}

static int
gtk_transition_state_property_compare_func (GtkTransitionStateProperty *property,
                                            const gchar                *property_name)
{
  return g_ascii_strcasecmp (property->name, property_name);
}

static GtkTransitionStateProgressFunc
gtk_transition_state_get_progress_func_for_type (GtkTransitionState *state,
                                                 GType               type)
{
  GtkTransitionStateProgressFunc func;

  switch (type)
    {
    case G_TYPE_INT:
      func = gtk_transition_state_int_progress_func;
      break;
    case G_TYPE_DOUBLE:
      func = gtk_transition_state_double_progress_func;
      break;
    case G_TYPE_BOOLEAN:
      func = gtk_transition_state_boolean_progress_func;
      break;
    default:
      func = NULL;
      break;
    }

  return func;
}

static void
gtk_transition_state_int_progress_func (GtkTransitionState *state,
                                        const gchar        *property_name,
                                        gdouble             progress,
                                        GValue             *v_result)
{
  GValue *v_start, *v_end;
  int start, end;

  gtk_transition_state_get_range (state, property_name, &v_start, &v_end);

  start = g_value_get_int (v_start);
  end = g_value_get_int (v_end);

  g_value_init (v_result, G_TYPE_INT);
  g_value_set_int (v_result,
                   (start + (end - start) * progress));
}

static void
gtk_transition_state_double_progress_func (GtkTransitionState *state,
                                           const gchar        *property_name,
                                           gdouble             progress,
                                           GValue             *v_result)
{
  GValue *v_start, *v_end;
  double start, end;

  gtk_transition_state_get_range (state, property_name, &v_start, &v_end);

  start = g_value_get_double (v_start);
  end = g_value_get_double (v_end);

  g_value_init (v_result, G_TYPE_DOUBLE);
  g_value_set_double (v_result,
                      (start + (end - start) * progress));
}

static void
gtk_transition_state_boolean_progress_func (GtkTransitionState *state,
                                            const gchar        *property_name,
                                            gdouble             progress,
                                            GValue             *v_result)
{
  g_value_init (v_result, G_TYPE_BOOLEAN);
  g_value_set_boolean (v_result, (progress == 1.0 ? TRUE : FALSE));
}

static void
gtk_transition_state_property_destroy (GtkTransitionStateProperty *property)
{
  if (G_IS_VALUE (&property->v_start))
    g_value_unset (&property->v_start);
  if (G_IS_VALUE (&property->v_end))
    g_value_unset (&property->v_end);
  g_free (property->name);
  g_free (property);
}
